﻿using System;
using System.Collections.Generic;
using System.Windows.Media;
using Geotools.Geometries;
using System.Linq;
using System.Xml.Linq;
using AzukiMap.Tools;

namespace AzukiMap.MapSource
{
    public class WMSSource : RasterSource
    {
        /*
        Request parameter Mandatory/optional Description
        VERSION=1.3.0 M Request version.
        REQUEST=GetMap M Request name.
        LAYERS=layer_list M Comma-separated list of one or more map layers.
        STYLES=style_list M Comma-separated list of one rendering style per requested layer.
        CRS=namespace:identifier M Coordinate reference system.
        BBOX=minx,miny,maxx,maxy M Bounding box corners (lower left, upper right) in CRS units.
        WIDTH=output_width M Width in pixels of map picture.
        HEIGHT=output_height M Height in pixels of map picture.
        FORMAT=output_format M Output format of map.
        TRANSPARENT=TRUE|FALSE O Background transparency of map (default=FALSE).
        BGCOLOR=color_value O Hexadecimal red-green-blue colour value for the background color (default=0xFFFFFF).
        EXCEPTIONS=exception_format O The format in which exceptions are to be reported by the WMS (default=XML).
        TIME=time O Time value of layer desired.
        ELEVATION=elevation O Elevation of layer desired.
        Other sample dimension(s) O Value of other dimensions as appropriate.
        */

        WMSVersion _version;
        string _request;
        List<string> _layers;
        List<string> _styles;
        //string _crs;
        bool _transparent;
        Color _bgcolor;
        string _exceptions;
        Dictionary<string, string> _otherParams;
        LatLonOrder _bBoxLatLonOrder;
        double _maxWidth;
        double _maxHeight;

        string _requestUri;
        string _title;
        string _abstruct;

        XmlCommon _xmlCommon;

        #region properties


        public WMSVersion Version
        {
            get { return _version; }
            set { _version = value; }
        }

        public string Request
        {
            get { return _request; }
            set { _request = value; }
        }

        public List<string> Layers
        {
            get { return _layers; }
            set { _layers = value; }
        }

        public List<string> Styles
        {
            get { return _styles; }
            set { _styles = value; }
        }

        public bool Transparent
        {
            get { return _transparent; }
            set { _transparent = value; }
        }

        public double MaxWidth
        {
            get { return _maxWidth; }
            set { _maxWidth = value; }
        }

        public double MaxHeight
        {
            get { return _maxHeight; }
            set { _maxHeight = value; }
        }

        public Color Bgcolor
        {
            get { return _bgcolor; }
            set { _bgcolor = value; }
        }

        public string Exceptions
        {
            get { return _exceptions; }
        }

        public Dictionary<string, string> OtherParams
        {
            get { return _otherParams; }
            set { _otherParams = value; }
        }

        public string RequestUri
        {
            get { return _requestUri; }
            set { _requestUri = value; }
        }

        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }

        public string Abstruct
        {
            get { return _abstruct; }
            set { _abstruct = value; }
        }

        public LatLonOrder BBoxLatLonOrder
        {
            get { return _bBoxLatLonOrder; }
            set { _bBoxLatLonOrder = value; }
        }

        public override Uri ImageUri
        {
            get { return GetUri(); }
        }

        #endregion

        public WMSSource()
            :base("wms raster", MapSourceTypes.Raster, 4326, CoordinationUnits.DEGREES)
        {
            init();
        }

        public WMSSource(string name, int srid, CoordinationUnits coordUnit)
            : base(name, MapSourceTypes.Raster, srid, coordUnit)
        {
            init();
        }

        void init()
        {
            _request = "GetMap";
            _version = WMSVersion.V1_1_1;
            _layers = new List<string>();
            _styles = new List<string>();
            _bbox = new GTEnvelope();
            _width = 0;
            _height = 0;
            _format = RasterFileSourceFormat.PNG;
            _transparent = false;
            _bgcolor = Colors.White;
            _exceptions = "application/vnd.ogc.se_inimage";
            _otherParams = new Dictionary<string, string>();
            _maxExtent = null;
            _bBoxLatLonOrder = LatLonOrder.LON_LAT;
            _maxWidth = int.MaxValue;
            _maxHeight = int.MaxValue;

            _xmlCommon = new XmlCommon();
        }

        public Uri GetUri()
        {
            string problemParams = checkMandatoryParams();
            if ( problemParams.Length != 0 )
            {
                throw new WmsParamsNotSetException("WMS Params are not set: " + problemParams);
            }

            string uriString =
                _requestUri
                + "?VERSION=" + GetWMSVersionString(_version)
                + "&REQUEST=" + _request
                + "&LAYERS=" + CreateCommaParam(_layers)
                + "&STYLES=" + CreateCommaParam(_styles)
                + "&" + CreateCrsStr() + "=EPSG:" + _srid.ToString()
                + "&BBOX=" + CreateBboxStr()
                + "&WIDTH=" + GetImageWidth().ToString()
                + "&HEIGHT=" + GetImageHeight().ToString()
                + "&FORMAT=" + WMSImageFormatToString(_format)
                + "&TRANSPARENT=" + _transparent.ToString().ToUpper()
                + "&BGCOLOR=" + _bgcolor
                + CreateOtherStr()
                ;

            Uri reqUri = new Uri(uriString);
            return reqUri;
        }

        string checkMandatoryParams()
        {
            string problemParams = "";
            if (_requestUri == null)
            {
                problemParams += "RequestUri ";
            }
            if (_layers.Count == 0)
            {
                problemParams += "Layers ";
            }
            if (_bbox.IsNull())
            {
                problemParams += "Bbox ";
            }
            if (_width == 0 )
            {
                problemParams += "Width ";
            }
            if (_height == 0)
            {
                problemParams += "Height ";
            }
            return problemParams;
        }

        string CreateCommaParam(List<string> paramList)
        {
            string sepStr = "";
            foreach (string p in paramList)
            {
                if (sepStr == "")
                {
                    sepStr = p;
                }
                else
                {
                    sepStr += "," + p;
                }
            }
            return sepStr;
        }

        string CreateBboxStr()
        {
            double minX = _bbox.MinX;
            double minY = _bbox.MinY;
            double maxX = _bbox.MaxX;
            double maxY = _bbox.MaxY;

            if (_width > _maxWidth)
            {
                double bWidth = maxX - minX;
                double centerX = (maxX + minX) / 2;
                double imgRatio = _maxWidth / _width;
                minX = centerX - bWidth * imgRatio / 2;
                maxX = centerX + bWidth * imgRatio / 2;
            }

            if (_height> _maxHeight)
            {
                double bHeight = maxY - minY;
                double centerY = (maxY + minY) / 2;
                double imgRatio = _maxHeight / _height;
                minY = centerY - bHeight * imgRatio / 2;
                maxY = centerY + bHeight * imgRatio / 2;
            }

            if (_bBoxLatLonOrder == LatLonOrder.LON_LAT)
            {
                return
                    minX.ToString() + "," +
                    minY.ToString() + "," +
                    maxX.ToString() + "," +
                    maxY.ToString();
            }
            else
            {
                return
                    minY.ToString() + "," +
                    minX.ToString() + "," +
                    maxY.ToString() + "," +
                    maxX.ToString();
            }
        }

        double GetImageWidth()
        {
            return (_width > _maxWidth) ? _maxWidth : _width;
        }

        double GetImageHeight()
        {
            return (_height > _maxHeight) ? _maxHeight : _height;
        }

        public double GetImageOffsetX()
        {
            if (_width > _maxWidth)
            {
                return (_width - _maxWidth) / 2;
            }
            else
            {
                return 0;
            }
        }

        public double GetImageOffsetY()
        {
            if (_height > _maxHeight)
            {
                return (_height - _maxHeight) / 2;
            }
            else
            {
                return 0;
            }
        }

        // v1.3.1 uses "CRS"
        // v1.1 uses "SRS"
        string CreateCrsStr()
        {
            string crsOrSrs;
            if (_version == WMSVersion.V1_3_0)
            {
                crsOrSrs = "CRS";
            }
            else
            {
                crsOrSrs = "SRS";
            }
            return crsOrSrs;
        }

        string CreateOtherStr()
        {
            string otherStr = "";
            foreach (KeyValuePair<string, string> keyAndValue in _otherParams)
            {
                otherStr += "&" + keyAndValue.Key + "=" + keyAndValue.Value;
            }
            return otherStr;
        }

        public string GetWMSVersionString(WMSVersion version)
        {
            string res = "";
            switch (version)
            {
                case WMSVersion.V1_1:
                    res = "1.1";
                    break;
                case WMSVersion.V1_1_1:
                    res = "1.1.1";
                    break;
                case WMSVersion.V1_3_0:
                    res = "1.3.0";
                    break;
            }
            return res;
        }

        public WMSVersion GetWMSVersionFromString(string versionStr)
        {
            WMSVersion version;
            switch (versionStr)
            {
                case "1.1":
                    version = WMSVersion.V1_1;
                    break;
                case "1.1.1":
                    version = WMSVersion.V1_1_1;
                    break;
                case "1.3.0":
                    version = WMSVersion.V1_3_0;
                    break;
                default:
                    version = WMSVersion.V1_1_1;
                    break;
            }
            return version;
        }

        public string WMSImageFormatToString(RasterFileSourceFormat format)
        {
            string res = "";
            switch (format)
            {
                case RasterFileSourceFormat.JPEG:
                    res = "image/jpeg";
                    break;
                case RasterFileSourceFormat.PNG:
                    res = "image/png";
                    break;
            }
            return res;
        }

        public RasterFileSourceFormat WMSImageFormatFromString(string formatStr)
        {
            RasterFileSourceFormat format;
            formatStr = formatStr.ToUpper();
            switch (formatStr)
            {
                case "JPEG":
                    format= RasterFileSourceFormat.JPEG;
                    break;
                case "PNG":
                    format= RasterFileSourceFormat.PNG;
                    break;
                default:
                    format= RasterFileSourceFormat.PNG;
                    break;
            }
            return format;
        }

        public override XElement ToXml()
        {
            /*
            <xs:attribute name="WmsVersion" type="WmsVersion" use="required" />
            <xs:attribute name="WmsRequest" type="WmsRequest" use="required" />
            <xs:attribute default="false" name="Transparent" type="xs:boolean" />
            <xs:attribute name="RequestUri" type="xs:string" use="required" />
            <xs:attribute name="Title" type="xs:string" />
            <xs:attribute name="MaxWidth" type="xs:positiveInteger" />
            <xs:attribute name="MaxHeight" type="xs:positiveInteger" />
            <xs:attribute name="BBoxLatLonOrder" type="LonLatOrder" />
            <xs:attribute name="Abstruct" type="xs:string" />
            <xs:attribute name="Format" type="RasterFormat" />
            <xs:element minOccurs="0" name="WmsLayers">
              <xs:complexType>
                <xs:choice>
                  <xs:element minOccurs="0" maxOccurs="unbounded" name="WmsLayer" type="xs:string" />
                </xs:choice>
              </xs:complexType>
            </xs:element>
            <xs:element name="WmsStyles">
              <xs:complexType>
                <xs:choice>
                  <xs:element minOccurs="0" maxOccurs="unbounded" name="WmsStyle" type="xs:string" />
                </xs:choice>
              </xs:complexType>
            </xs:element>
            <xs:element name="BgColor" type="Color" />
            <xs:element name="OtherParams">
              <xs:complexType>
                <xs:choice>
                  <xs:element name="OtherParam">
                    <xs:complexType>
                      <xs:attribute name="Key" type="xs:string" use="required" />
                      <xs:attribute name="Value" type="xs:string" use="required" />
                    </xs:complexType>
                  </xs:element>
                </xs:choice>
              </xs:complexType>
            </xs:element>
            <xs:element name="Abstruct" type="xs:string" />
             */
            //XElement mapSourceXml = base.ToXml();
            XElement wmsSourceXml = base.ToXml("WmsSource");
            wmsSourceXml.Add(
                _xmlCommon.CreateAttr("WmsVersion", _version),
                _xmlCommon.CreateAttr("WmsRequest", _request),
                _xmlCommon.CreateAttr("Transparent", _transparent),
                _xmlCommon.CreateAttr("BBoxLatLonOrder", _bBoxLatLonOrder),
                _xmlCommon.CreateAttr("RequestUri", _requestUri),
                _xmlCommon.CreateAttr("MaxWidth", _maxWidth),
                _xmlCommon.CreateAttr("MaxHeight", _maxHeight),
                _xmlCommon.CreateAttr("Title", _title),

                _xmlCommon.ColorToXml("BgColor", _bgcolor),
                _xmlCommon.CreateElement("Abstruct", _abstruct),
                _xmlCommon.CreateElement("WmsLayers",
                    from layer in _layers
                    select new XElement("WmsLayer", layer)
                    ),
                _xmlCommon.CreateElement("WmsStyles",
                    from style in _styles
                    select new XElement("WmsStyle", style)
                    ),
                _xmlCommon.CreateElement("OtherParams", 
                    from param in _otherParams
                    select new XElement("OtherParam", 
                        new XAttribute("Key", param.Key),
                        new XAttribute("Value", param.Value)
                        )
                    )
                );
            //mapSourceXml.Add(wmsSourceXml);

            //return mapSourceXml;
            return wmsSourceXml;
        }

        public override void Parse(XElement mapSource)
        {
            base.Parse(mapSource);
            XElement wmsSource = mapSource.Element("WmsSource");
            _version = (WMSVersion)_xmlCommon.GetEnumElement(wmsSource, "WmsVersion", typeof(WMSVersion));
            _request = _xmlCommon.GetStringElement(wmsSource, "WmsRequest");
            _transparent = _xmlCommon.GetBoolElement(wmsSource, "Transparent");
            _bgcolor = _xmlCommon.GetColorElement(wmsSource.Element("BgColor"));
            _bBoxLatLonOrder = (LatLonOrder)_xmlCommon.GetEnumElement(wmsSource, "BBoxLatLonOrder", typeof(LatLonOrder));
            _maxWidth = (int)_xmlCommon.GetIntAttr(wmsSource, "MaxWidth");
            _maxHeight = (int)_xmlCommon.GetIntAttr(wmsSource, "MaxHeight");
            _format = (RasterFileSourceFormat)_xmlCommon.GetEnumElement(wmsSource, "Format", typeof(RasterFileSourceFormat));
            _requestUri = _xmlCommon.GetStringAttr(wmsSource, "RequestUri");
            _title = _xmlCommon.GetStringElement(wmsSource, "Title");
            _abstruct = _xmlCommon.GetStringElement(wmsSource, "Abstruct");
            _layers = (List<string>)
                from layer in wmsSource.Element("WmsLayers").Elements("WmsLayer") 
                select layer; 
            _styles = (List<string>)
                from style in wmsSource.Element("WmsStyles").Elements("WmsStyle") 
                select style; 
            _otherParams = _xmlCommon.SetElementValuesToDict(wmsSource.Element("OtherParams").Elements());

        }

    }

    

    public enum WMSVersion
    {
        V1_1,
        V1_1_1,
        V1_3_0,
    }

    public enum LatLonOrder
    {
        LON_LAT = 0,
        LAT_LON = 1,
    }
}
